CREST Adapter#807
Open
calvinp0 wants to merge 187 commits into
Open
Conversation
d6a83b4 to
c231a43
Compare
3e88c36 to
7674f5f
Compare
9c25f3d to
51368be
Compare
9be935e to
eab7647
Compare
When a TS did not converge, its spawned IRC endpoint species (e.g. IRC_TS0_1) is deleted. The scheduler removed it from self.output and self.species_dict, but removed it from self.species_list via reassignment (self.species_list = [...]), which rebinds only the scheduler's variable and leaves the deleted species dangling in ARC.species (the same list object, mutated in-place by the earlier append). save_project_info_file then iterates ARC.species and looks each up in the synced self.output, raising KeyError and crashing the run at the final summary step after all work was done. Fix: (1) delete in-place via slice assignment so removals propagate through the shared list like the appends do; (2) guard the summary loop to skip any species missing from self.output (deleted IRC species shouldn't be reported anyway). Adds a regression test.
Follow-up to the IRC-species KeyError fix: save_project_info_file has a SECOND loop (building <project>_info.yml for T3) with the same unguarded self.output[species.label] lookup, so a dangling deleted IRC species would KeyError there as well once the first loop was guarded. Guard it identically (skip species not in self.output). Also rewrite the regression test to exercise save_project_info_file on a bare ARC instance (ARC.__new__ + SimpleNamespace stand-ins) instead of constructing a full ARC object. The fix is pure bookkeeping and does not need the Arkane BAC tables / molecule perception that a full ARC build pulls in; the test now runs in ~1s instead of minutes and has no RMG dependency.
…ml guard + lean test)
…actions For reactions where a reactant participates more than once (e.g. OH + OH -> H2O + O), r_species is deduplicated, which broke the normal-mode-displacement check two ways: 1. n_expected was sum(number_of_atoms for r_species) = the DEDUPED atom count (2 for OH+OH), so it never equaled the real TS atom count (4) and analyze_ts_normal_mode_displacement returned False before even looking at the mode. Count each species by its occurrence via get_species_count (the same dedup-aware pattern get_reactants_xyz already uses). 2. find_equivalent_atoms only found equivalence WITHIN a molecule, so it missed the cross-molecule equivalence between identical copies (the two O's, the two H's of OH+OH). The atom map can assign the reactive atom to one copy while the located TS uses the equivalent atom of the other copy; without the equivalence NMD cannot try the correct mapping. Added get_repeated_species_atom_equivalences: for a repeated species, atoms at the same position across copies are equivalent. Validated on the real OH+OH -> H2O+O saddle (imag -768 cm-1) from the reproducibility runs: NMD now returns True (was False). Recovers reaction 05 and other same-species H_Abstractions. Continuation of the repeated-reactant dedup work (get_reactants_xyz / autotst reverse fix).
…A NEB level
Two issues surfaced running the QST2 adapter (reaction 21, intra_halogen_migration):
1. process_completed_tsg_queue_jobs hard-coded method='orca_neb' for every .log queue TS-guess
job. Now that more than one queue adapter emits a .log (orca_neb and qst2), QST2's guess was
collected but mislabeled 'orca_neb'. Thread the actual adapter through: the method now takes a
`method` argument and the scheduler passes job.job_adapter.
2. orca_neb_settings['level'] used 'wb97xd/def2tzvp', but ORCA rejects Gaussian's 'wb97xd' spelling
("UNRECOGNIZED KEYWORD WB97XD"), so every NEB job errored and produced no geometry to parse
("Fallback parsing failed"). Use the ORCA spelling 'wb97x-d3/def2tzvp'.
Adds a regression test asserting a .log queue guess is attributed to 'qst2', not 'orca_neb'.
QST2 was generating its guess at wb97xd/def2tzvp, but ARC re-optimizes every TS guess at the ts_guesses level (wb97xd/def2svp) in conf_opt before the final project-level opt. Doing the QST2 opt=qst2 at the larger def2tzvp basis was wasteful (its precision is discarded by the def2svp conf_opt). Match the guess level to the TS-guess opt level.
| 'level': 'wb97x-d3/def2tzvp', # ORCA spelling; it does not accept Gaussian's 'wb97xd' | ||
| } | ||
|
|
||
| qst2_settings = {'maxcycle': 150, |
Check notice
Code scanning / CodeQL
Unused global variable Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 9 hours ago
To fix this without changing functionality, rename the variable to an intentionally-unused name that CodeQL recognizes, while preserving the dictionary value and comments.
Best single change in arc/settings/settings.py:
- Change
qst2_settings = {...}to_unused_qst2_settings = {...}at the shown block around line 273. - No imports, methods, or additional definitions are needed.
This keeps runtime behavior equivalent for this module snippet and satisfies the “intentionally unused” naming convention accepted by the query.
Suggested changeset
1
arc/settings/settings.py
| @@ -270,9 +270,9 @@ | ||
| 'level': 'wb97x-d3/def2tzvp', # ORCA spelling; it does not accept Gaussian's 'wb97xd' | ||
| } | ||
|
|
||
| qst2_settings = {'maxcycle': 150, | ||
| 'level': 'wb97xd/def2svp', # match the TS-guess opt level; the guess is refined later | ||
| } | ||
| _unused_qst2_settings = {'maxcycle': 150, | ||
| 'level': 'wb97xd/def2svp', # match the TS-guess opt level; the guess is refined later | ||
| } | ||
|
|
||
| ase_default_options_dict = {'optimizer': 'BFGS', | ||
| 'fmax': 0.001, |
Copilot is powered by AI and may make mistakes. Always verify output.
When Arkane emits errors but still produces output.py (e.g. a first attempt that failed on the
Eckart submerged-barrier ValueError and was then retried with tunneling off), run_arkane dumped the
entire multi-line Python traceback into arc.log at WARNING. Log the salient exception line plus a
count instead ("ValueError: ... [+N more stderr line(s)]"), preferring the real Python exception
over the conda wrapper's non-zero-exit line. Same condensing applied to the genuine-failure ERROR
path (full traceback remains in the run dir's stderr.log). Adds tests.
The SCF quadratic-convergence remedy was guarded on the token 'no_qc',
which is never appended to ess_trsh_methods; the token actually recorded
by trsh_keyword_no_qc() (and consumed by the Gaussian adapter) is 'no_xqc'.
As a result, after an l508 failure dropped 'scf=(qc)' and recorded 'no_xqc',
the next SCF failure re-added 'scf=(qc)', the next l508 failure dropped it
and appended 'no_xqc' again, and so on - wasting the retry budget and never
reaching the rest of the SCF ladder (NDamp, NoDIIS, guess=INDO, Fermi, ...).
- Align the three 'no_qc' guards in trsh.py to 'no_xqc'.
- Count a dropped 'scf=(qc)' (recorded as 'no_xqc') as attempted for the
Fermi/Noincfock/NoVarAcc last-resort gate, which previously required
'scf=(qc)' to still be present and was unreachable on the no_xqc path.
- Make the 'no_xqc' append idempotent and fix the copy-paste docstring of
trsh_keyword_no_qc().
- Gaussian adapter: block the qc->xqc upgrade based on ess_trsh_methods;
the old check ('no_xqc' in self.args['trsh'].values()) can never match
the scheduler-built args shape {'trsh': [keyword_list]}.
- Add a retry-cycle simulation test (trsh_test) and adapter input tests
(gaussian_test) that fail on the old behavior.
…g, pin ladders
Holistic review cleanup of the Gaussian ESS troubleshooting cycle (no behavior change):
- trsh_keyword_inaccurate_quadrature: remove the final elif branch. It guarded on
'int=ultrafine', a token never added to ess_trsh_methods anywhere, so the guard was
always true; the branch never advanced ess_trsh_methods and only re-appended already-
present trsh keywords (deduped downstream by combine_parameters), firing solely on the
terminal retry that trsh_ess_job discards via 'all_attempted'. Replaced with an accurate
comment. Proven behavior-preserving by the new ladder test (passes pre- and post-removal).
- trsh_keyword_unconverged: correct the copy-paste docstring ('change of scf' -> fine-grid
remedy for a generic l9999 Unconverged failure).
- Add regression tests pinning the InaccurateQuadrature and MaxOptCycles remedy ladders
(escalation order + termination), which also document current behavior for the review.
…nup: dead int=ultrafine branch + docstring)
trsh_keyword_intaccuracy fired on every Gaussian error (no error-class guard), so input/method errors that a resubmit cannot fix (Syntax, InputError, ZMat, MP2, OptOrientation, Scratch, l401 BasisSet-projection) each picked up a spurious int=(Acc2E=14) step and burned a resubmit before hitting all_attempted. Add GAUSSIAN_NON_RETRYABLE_KEYWORDS and a refusal branch in the shared if/elif chain so these classes set couldnt_trsh=True immediately with a clear error, never entering the helper cascade. Deliberately conservative: SCF, opt-cycles, internal-coordinate, negative-eigenvalue, memory, checkfile and the generic 'Unknown' (scheduler retries on another node) classes are untouched. Test asserts the seven non-retryable classes refuse with no remedy appended, and that SCF and MaxOptCycles still receive their remedies unchanged.
The adapter only emitted integral_algorithm under 'if self.fine', so a non-fine opt (or IRC) whose only troubleshooting remedy was int=(Acc2E=14) produced a route byte-identical to the un-troubleshot one - a guaranteed no-op resubmit. Capture acc2e_requested and, for non-fine opt/IRC jobs, emit a bare integral=(Acc2E=14) via the route's fine slot so the tightened integral accuracy actually takes effect. Fine jobs still fold Acc2E into the ultrafine grid; normal non-fine jobs (no Acc2E trsh) are unchanged. Render tests cover: non-fine opt emits it, non-fine opt without the trsh is unchanged, fine opt still folds into ultrafine, and non-fine IRC emits it.
Rework the MaxOptCycles remedy ladder so it recomputes the Hessian before flipping the
step algorithm, and is transition-state aware:
maxcycle=200 -> recalcfc=5 -> calcall -> RFO -> [GDIIS -> GEDIIS, minimization only]
Recomputing force constants (recalcfc every 5 steps, then calcall every step) is the
first-line remedy for a stuck optimization and is essential for a TS opt, where following
the single negative eigenvalue depends on Hessian quality. GDIIS/GEDIIS are minimization-
oriented DIIS step accelerators that can walk a TS opt downhill into a nearby minimum and
lose the saddle, so they are skipped when is_ts is True (RFO/eigenvector-following is kept).
- Thread is_ts from the scheduler (species.is_ts) through trsh_ess_job into
trsh_keyword_opt_maxcycles and prioritize_opt_methods.
- prioritize_opt_methods: keep a single step algorithm (TS -> RFO only) and collapse the
force-constant family to the most aggressive directive (calcall > recalcfc=* > calcfc).
- Gaussian adapter: drop the base-route calcfc when the ladder supplies recalcfc/calcall,
so the opt=() clause never carries conflicting Hessian options.
Tests: new min/TS ladder tests + prioritize unit test (trsh_test); adapter FC-resolution
and TS-route render tests (gaussian_test). Updated the pre-existing test_trsh_ess_job
step-ladder and the job_23/job_24 route fixtures to the new ladder (routes verified by
rendering). Note: gaussian_test::test_trsh_write_input_file has a pre-existing content_21
(InaccurateQuadrature) failure identical on the parent branch, unrelated to this change.
…e non-retryable, P2 Acc2E non-fine, P3 TS-aware Hessian ladder)
…rom_dict ARCSpecies.from_dict (the path main.py uses to load input.yml species) builds self.mol from a spin-agnostic SMILES/InChI (Molecule(smiles=...)), which RMG perceives at a default spin state, but never reconciled it to the declared multiplicity -- the reconciliation only ran when multiplicity was None. So a singlet carbene [CH2] with multiplicity: 1 ended up with a TRIPLET .mol (u2, mult 3) while self.multiplicity was 1. Downstream graph-based logic (RMG family determination, 3D<->2D isomorphism checks) then saw a triplet: RMG's 1+2_Cycloaddition family requires a singlet carbene, so it failed to match -> family=None -> the reaction fell back to ts_adapters_for_unknown_unimolecular=[linear] and was silently under-served (reaction_01). The kwargs __init__ path already reconciles; from_dict did not. Fix: mirror the kwargs path -- call reconcile_mol_multiplicity() in from_dict when the mol was built from a spin-agnostic descriptor (smiles/inchi, not adjlist/mol), an explicit multiplicity is given and disagrees with the perceived mol, and number_of_radicals is not declared (which would mark an intentional open-shell state). reconcile_mol_multiplicity() only pairs same-atom radicals into a lone pair and no-ops otherwise, so triplet carbene, O2, OH, and closed-shell species are untouched. Validated: from_dict [CH2] mult 1 -> C u0 p1; reaction C=C + [CH2](m1) -> cyclopropane now resolves to family 1+2_Cycloaddition (was None), so it gets kinbot/xtb_gsm/orca_neb/linear instead of linear-only. General fix for any spin-ambiguous species (singlet carbenes, etc.) loaded from an input file.
…-derived bonds The reaction atom map is only determined up to permutations of chemically equivalent atoms. For reaction CCO[O] <=> C=C + [O]O (HO2 elimination, benchmark reaction 04) the map assigns the heavy atoms and the transferring beta-H correctly, but crosses the four spectator H atoms between the two carbons of the ethylene product (all four H positions there are equivalent, so the map is chemically valid). Pulling product bonds back through such a permuted map inflates the formed/broken sets to 5 formed + 6 broken bonds (instead of 1 formed + 2 broken + 1 changed), and NMD then falsely rejected the genuine concerted-elimination saddle (imag ~-1074 1/cm) because the spurious spectator C-H 'reactive' bonds show no motion along the mode. Fix: derive candidate reactive-bond sets in NMD via the new get_bond_change_candidates(), which tries the RMG-family-recipe-derived bonds (canonical, atom-map independent; the path already existed for the atom_map-is-None case) first, keeping the atom-map-derived sets as a fallback candidate. Renamed ARCReaction._get_reactive_bonds_from_family to the public get_reactive_bonds_from_family so NMD can use it. Verified against the real benchmark saddles: the -1074 saddle now passes NMD while the spurious -2144, -238, and -531 saddles of the same TS search still fail; all existing NMD fixtures classify unchanged.
…ecipe reactive bonds as a candidate)
A conf_opt that drove atoms collinear during optimization hit Gaussian L716
('Angle in z-matrix outside the allowed range 0 < x < 180', keyword 'ZMat') and
was abandoned immediately, because 'ZMat' was in GAUSSIAN_NON_RETRYABLE_KEYWORDS.
But ARC always submits Cartesian geometries, so a ZMat error is never a bad input -
it is the textbook collinear-angle degeneracy that Cartesian optimization sidesteps.
- Remove 'ZMat' from GAUSSIAN_NON_RETRYABLE_KEYWORDS (Syntax/InputError/MP2/
OptOrientation/Scratch/BasisSet remain genuine dead ends).
- Make trsh_keyword_cartesian fire on 'ZMat' as well as 'InternalCoordinateError'
(L103), so a ZMat error records 'cartesian' and emits opt=(cartesian) on the retry.
- Termination is preserved: opt=(cartesian) is tried once (guarded by 'cartesian'
not in ess_trsh_methods); a recurring ZMat leaves ess_trsh_methods unchanged, so
trsh_ess_job terminates via 'all_attempted'. Verified for opt and conf_opt.
Before: ZMat -> immediate refusal (couldnt_trsh=True, ess=[]).
After: ZMat -> opt=(cartesian) resubmit, then terminate if it recurs.
Tests: ZMat yields opt=(cartesian) and is not refused; a second ZMat after cartesian
terminates (opt and conf_opt); the other non-retryable classes still refuse.
…t=(cartesian) instead of non-retryable)
…, TSs) Previously any species with >1 atom received guess=mix on fresh jobs regardless of electronic structure. guess=mix seeds a broken-symmetry initial guess, which is only meaningful for open-shell (biradical) singlets and TS optimizations; on restricted closed-shell routes it merely perturbs the initial guess (Gaussian still applies the HOMO/LUMO mixing but the R wavefunction cannot break spin symmetry), and simple high-spin radicals converge to the correct state from the default guess. Benchmark evidence (arcbench, 911 Gaussian jobs): doublets seeded with guess=mix show <S**2> = 0.7524-0.7787 (mean 0.7555) before annihilation, statistically identical to guess=read-seeded doublets, so dropping mix for them is safe; all 431 singlet jobs ran restricted and converged. guess=read (checkfile) and guess=INDO (troubleshooting) precedence is unchanged.
…s=mix to TS + singlet biradicals)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addition of CREST Adapter that complements the heuristic adapter.
This pull request adds support for the CREST conformer and transition state (TS) search method to the ARC project, along with several related improvements and code cleanups. The most important changes include integrating CREST as a TS search adapter, updating configuration and constants, and enhancing the heuristics TS search logic for better provenance tracking and code clarity.
CREST Integration:
JobEnum(arc/job/adapter.py), included CREST in the list of adapters and RMG family mapping, and registered it as a default incore adapter (arc/job/adapters/common.py,arc/job/adapters/ts/__init__.py). [1] [2] [3] [4]arc/job/adapters/ts/crest_test.py).Makefile). [1] [2]Constants and Configuration:
angstrom_to_bohrconversion constant to both Cython and Python constants modules (arc/constants.pxd,arc/constants.py). [1] [2] [3]Heuristics TS Search Enhancements and Refactoring:
arc/job/adapters/ts/heuristics.py). [1] [2] [3] [4]arc/job/adapters/ts/heuristics.py). [1] [2] [3] [4] [5] [6] [7].